home *** CD-ROM | disk | FTP | other *** search
/ Internet Info 1994 March / Internet Info CD-ROM (Walnut Creek) (March 1994).iso / networking / info-service / gopher / Unix / GopherTools / glog / glog33.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-07-20  |  33.0 KB  |  1,400 lines

  1. /*** glog33.c -- analysis tool for Unix gopherd logs ***/
  2.  
  3. /* Define NODETAIL if you don't want detail listings to be kept. Detail 
  4.  * listings can double the pointer memory required and slow things down.
  5.  * If you NEVER want to do detail listings then define it.  On fast machines
  6.  * it really doesn't matter, but on my Amiga I can notice the difference.
  7.  */
  8.  
  9. /* #define NODETAIL */
  10.  
  11. /******************** END CONFIGURATION ***************************/
  12. /* That was really hard wasn't it :) */
  13.  
  14.  
  15. /* Bug Reports/suggestions goto */
  16. #define EMAIL "awick@csugrad.cs.vt.edu"
  17.  
  18. #define    GLOG_VERSION "Gopher Log Analyzer 3.3"
  19.  
  20. /* Version 3.3 7/20/93 jdc@selway.umt.edu
  21.  * fixed up main() routine so that: errors on the command line (or -h as
  22.  * first parameter) cause glog to print the help information and abort.
  23.  * fixed up PrintHelp() routine so help message is more understandable
  24.  * changed FILETYPE character from ' ' to 'I' (What about Image?)
  25.  *
  26.  */
  27. /* Version 3.2 
  28.  * Fixed a small bug with search
  29.  */
  30. /* Version 3.1  7/11/93
  31.  * by: Andy Wick - awick@csugrad.cs.vt.edu
  32.  * Added supported for the "missing" gopher types, time plots, month
  33.  * reports and several other things that I forgot :)
  34.  * Used unprotize from gnu to unprototype the functions for external
  35.  * distrubition.  (My version still has prototypes)
  36.  *
  37.  * Version 3.0 
  38.  * by: Andy Wick - awick@csugrad.cs.vt.edu
  39.  * This version is an almost TOTAL rewrite of glog.  It now reads all
  40.  * the information into memory before it does ANYTHING.  It then goes
  41.  * through the arguments one at a time.  So inorder to effect something
  42.  * you must change it before the report.  ie.  Argument order matters now.
  43.  *
  44.  * Version 2.2
  45.  * by: Chuck Shotton - cshotton@oac.hsc.uth.tmc.edu
  46.  *
  47.  * Version 2.1 
  48.  * by: Michael Mealling - Georgia Institute of Technology
  49.  *       Office of Information Technology
  50.  *       michael.meallingl@oit.gatech.edu
  51.  *       12/29/92
  52.  *
  53.  * Versions 1.0
  54.  * by: Chuck Shotton - U of Texas Health Science Center - Houston,
  55.  *    Office    of Academic Computing
  56.  *    cshotton@oac.hsc.uth.tmc.edu
  57.  *    6/17/92
  58.  */
  59.  
  60.  
  61. #include <stdio.h>
  62. #include <string.h>
  63. /* Some machines don't have a stdlib.  You can remove it if need be
  64.  *  it is here for type checking, usually :) 
  65.  */
  66. #include <stdlib.h>
  67. #ifdef THINK_C
  68. #include <console.h> 
  69. #endif
  70.  
  71.  
  72. /* GENERAL STUFF */
  73. typedef unsigned char byte;
  74.  
  75. /* Error log link list */
  76. typedef    struct enode_list {
  77.     char      *data;
  78.     struct enode_list *next;
  79. } ELIST_REC, *ELIST_PTR;
  80.  
  81. /* GOPHER LINE STUFF */
  82.  
  83. /* These are the different types of data that are currenly reconized*/
  84. #define FILETYPE    'I'
  85. #define BINARYTYPE    'B'
  86. #define SOUNDTYPE    'S'
  87. #define DIRTYPE        'D'
  88. #define MAILDIRTYPE    'M'
  89. #define FTPTYPE        'F'
  90. #define RANGETYPE    'R'
  91. #define SEARCHTYPE    '?'
  92.  
  93. /* One line of the gopher log is stored in here */
  94. typedef struct gopher_line {
  95.    byte     day;
  96.    byte     month;
  97.    byte        hour;
  98.    short     date;
  99.    char     *hostname;
  100.    char     *path;
  101.    char     type;
  102. } GOPHER_REC, *GOPHER_PTR;
  103.  
  104. /* A Linked list of gopher lines */
  105. typedef    struct node_list {
  106.     GOPHER_PTR      data;
  107.         short         hits;
  108.     struct node_list *next;
  109. }  LIST_REC, *LIST_PTR;
  110.  
  111.  
  112. /* Main tree */
  113. typedef    struct node_rec    {
  114.     GOPHER_PTR    data;
  115. #ifndef NODETAIL
  116.     LIST_REC     *llist;
  117. #endif
  118.         short         hits;
  119.     struct node_rec    *left, *right;
  120. } NODE_REC, *NODE_PTR;
  121.  
  122. /* The cruft list is a general list for things that aren't parse-able by
  123.  * ProcessLine().  "cruft" kept for historical reasons.
  124.  */
  125. ELIST_PTR     cruft = NULL;
  126.  
  127. /*
  128.  * The following lists are maintained.
  129.  */
  130. NODE_PTR     hosts = NULL;
  131. NODE_PTR     docs  = NULL;
  132. NODE_PTR     days  = NULL;
  133. NODE_PTR     dates = NULL;
  134. NODE_PTR     types = NULL;
  135. NODE_PTR     times = NULL;
  136.  
  137. long int     TotalConnects = 0;
  138.  
  139. /*
  140.  * Self-Documenting vars, that save memory
  141.  */
  142. char         *ROOTNAME = "Root Connections";
  143. char        *Days[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  144. char        *Months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
  145.                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  146.  
  147. /* The base file name for gnuplot reports */
  148. char BASE[7] = "gopher";
  149. char *base = BASE;
  150.  
  151. /* Plot Output. NOT SUPPORTED YET */
  152. #define REPORTOUT    0
  153. #define GNUOUT         1
  154. #define HISTOUT        2
  155.  
  156. /* Type of plot to do */
  157. char OutPlot=GNUOUT;
  158.  
  159. /* Used to tell the Plot routines that you are starting and stoping */
  160. #define PLOTSTART    (GOPHER_PTR)0
  161. #define PLOTDONE    (GOPHER_PTR)1
  162.  
  163. /* Width of reports */
  164. byte     Width     =     62; /* 79 - WIDTHSUB */
  165. #define WIDTHSUB     17  /* The width of the standard print stuff */
  166.  
  167. /* Information */
  168.    /* Internal */
  169. #define NOINFO        0
  170. #define DETAILINFO    1
  171.    /* Changing these will change the command line options also */
  172.    /* Error log requested, but not a valid SORT TYPE */
  173. #define ERRORINFO    'E'
  174.    /* SORT TYPES */
  175. #define DATAINFO     'D'
  176. #define HOSTINFO    'H'
  177. #define WEEKDAYINFO    'W'
  178. #define MONTHDATEINFO    'M'
  179. #define TYPEINFO    'T'
  180. #define TIMEINFO    'I'
  181.  
  182. /* Start stop dates */
  183. char start_date[13], stop_date[13];
  184.  
  185. /* Start stop months */
  186. int mbegin = 1, mend = 12;
  187.  
  188. /* The only forward decleration needed, since I wrote this the pascal way,
  189.  * the way all programs should be written. :) You don't need all the stupid
  190.  * forward declerations, or prototypes.
  191.  */
  192. void PrintInfo(NODE_PTR tree, void print(GOPHER_PTR), int cmp(GOPHER_PTR a, GOPHER_PTR b), byte DetailType);
  193.  
  194.  
  195. /*******************************/
  196. /* My StrStr, since it is not standard on all unix machines (sun 4.0.3) */
  197. char *mystrstr(char *s1, char *s2)
  198. {
  199. register int len;
  200.  
  201.    len = strlen(s2);
  202.  
  203.    if (len == 0)
  204.       return s1;
  205.  
  206.    while (*s1 != '\0')
  207.    {
  208.       if (strncmp(s1, s2, len) == 0)
  209.          return s1;
  210.       s1++;
  211.    }
  212.  
  213.    return NULL;
  214. }
  215. /*******************************/
  216. /* Add item to error log */
  217. ELIST_PTR InsertErrorLog(ELIST_PTR list, char *data)
  218. {
  219. ELIST_PTR temp, temp2;
  220.  
  221.    if (NULL == (temp = (ELIST_PTR)malloc(sizeof(ELIST_REC))))
  222.    {
  223.       fprintf(stderr, "Not enough memory to add to ErrorLog\n");
  224.       exit(1);
  225.    }
  226.    if (NULL == (temp->data = (char *)malloc(sizeof(char) * (strlen(data) +1))))
  227.    {
  228.       fprintf(stderr, "Not enough memory to add to ErrorLog\n");
  229.       exit(1);
  230.    }
  231.    strcpy(temp->data, data);
  232.    temp->next = NULL;
  233.  
  234.    if (list == NULL)
  235.       return (temp);
  236.  
  237.    for (temp2 = list; temp2->next != NULL ; temp2 = temp2->next);
  238.    temp2->next = temp;
  239.  
  240.    return(list);
  241. }
  242. #ifndef NODETAIL
  243. /*******************************/
  244. LIST_PTR InsertDetail(LIST_PTR list, GOPHER_PTR data)
  245. {
  246. LIST_PTR temp;
  247.  
  248.       if (NULL == (temp = (LIST_PTR)malloc(sizeof(LIST_REC))))
  249.       {
  250.          fprintf(stderr, "Not enough memory to add to DetailList\n");
  251.          exit(1);
  252.       }
  253.       temp->data = data;
  254.       temp->next = list;
  255.       temp->hits = 1;
  256.       return(temp);
  257. }
  258. #endif
  259. /*******************************/
  260. /* Insert tree_element into the    appropriate symbol table. Increment the
  261.  * number of hits if that element is already present.
  262.  * Insert list_element into linked list    contained in the node that
  263.  * tree_element    was put    in.
  264.  */
  265.  
  266. NODE_PTR Insert(NODE_PTR tree, GOPHER_PTR data, int cmp(GOPHER_PTR a, GOPHER_PTR b))
  267. {
  268. int i;
  269.  
  270.    if (tree == NULL)
  271.    {
  272.       if (NULL == (tree = (NODE_PTR) malloc(sizeof(NODE_REC))))
  273.       {
  274.          fprintf(stderr, "No memory for InsertHost\n");
  275.      exit(1);
  276.       }
  277.       tree->data = data;
  278.       tree->left = tree->right = NULL;
  279. #ifndef NODETAIL
  280.       tree->llist = InsertDetail(NULL, data);
  281. #endif
  282.       tree->hits = 1;
  283.       return(tree);
  284.    }
  285.  
  286.    i=cmp(data, tree->data);
  287.  
  288.    if (i > 0)
  289.       tree->right = Insert(tree->right, data, cmp);
  290.    else if (i<0) 
  291.       tree->left = Insert(tree->left, data, cmp);
  292.    else
  293.    {
  294.       tree->hits += 1;
  295. #ifndef NODETAIL
  296.       tree->llist = InsertDetail(tree->llist, data);
  297. #endif
  298.    }
  299.  
  300.    return(tree);
  301. }
  302.  
  303. /*******************************/
  304. NODE_PTR Find(NODE_PTR tree, GOPHER_PTR data, int cmp(GOPHER_PTR a, GOPHER_PTR b))
  305. {
  306. int i;
  307.  
  308.    if (tree == NULL)
  309.    {
  310.       return (NULL);
  311.    }
  312.  
  313.    i=cmp(data, tree->data);
  314.  
  315.    if (i > 0)
  316.       return(Find(tree->right, data, cmp));
  317.    if (i<0) 
  318.       return(Find(tree->left, data, cmp));
  319.  
  320.    return(tree);
  321. }
  322. /*******************************/
  323. /* Get a single field from temp, and return the new spot */
  324. char *getf(char *temp, char *field)
  325. {
  326.    while(*temp == ' ')
  327.       temp++;
  328.  
  329.    *field = '\0';
  330.    if (*temp == '\n')
  331.       return(temp);
  332.  
  333.    while ((*temp != ' ') && (*temp != '\0'))
  334.      *field++ = *temp++;
  335.  
  336.    *field = '\0';
  337.    return(temp);
  338. }
  339.  
  340. /*******************************/
  341. int TypesCmp(GOPHER_PTR a, GOPHER_PTR b)
  342. {
  343.    return(a->type - b->type);
  344. }
  345. /*******************************/
  346. int TimesCmp(GOPHER_PTR a, GOPHER_PTR b)
  347. {
  348.    return(a->hour - b->hour);
  349. }
  350. /*******************************/
  351. int HostsCmp(GOPHER_PTR a, GOPHER_PTR b)
  352. {
  353.    return(strcmp(a->hostname, b->hostname));
  354. }
  355. /*******************************/
  356. int DocsCmp(GOPHER_PTR a, GOPHER_PTR b)
  357. {
  358.    return(strcmp(a->path, b->path));
  359. }
  360. /*******************************/
  361. int DaysCmp(GOPHER_PTR a, GOPHER_PTR b)
  362. {
  363.    return(a->day - b->day);
  364. }
  365. /*******************************/
  366. int DatesCmp(GOPHER_PTR a, GOPHER_PTR b)
  367. {
  368. int i = a->month - b->month;
  369.    if (i == 0)
  370.       return(a->date - b->date);
  371.    else
  372.       return(i); 
  373. }
  374. /*******************************/
  375. byte MonthStr2Num(char *str)
  376. {
  377. static char lastmonth[4] = "Jan"; /* Who knows if saving the last month */
  378. static int lastmonthnum = 1;      /* really makes it faster */
  379. int i;
  380.  
  381.    if (strcmp(lastmonth, str) == 0)
  382.       return(lastmonthnum);
  383.  
  384.    for(i=0;i<12;i++)
  385.       if (strcmp(Months[i], str) == 0)
  386.       {
  387.          strcpy(lastmonth, Months[i]);
  388.      lastmonthnum = i+1;
  389.      return(lastmonthnum);
  390.       }
  391.    return(13); 
  392. }
  393. /*******************************/
  394. byte DayStr2Num(char *str)
  395. {
  396. static char lastday[4] = "Sun"; /* Same here.  Is there a better way? */
  397. static int lastdaynum = 1;
  398. int i;
  399.  
  400.    if (strcmp(lastday, str) == 0)
  401.       return(lastdaynum);
  402.  
  403.    for(i=0;i<7;i++)
  404.       if (strcmp(Days[i], str) == 0)
  405.       {
  406.          strcpy(lastday, Days[i]);
  407.      lastdaynum = i+1;
  408.      return(lastdaynum);
  409.       }
  410.    return(8); 
  411. }
  412. /*******************************/
  413. /* This routine adds all the data to all the trees.  Now that I have done
  414.  * it this way, I really hate it.  What was I thinking? :)  It makes no
  415.  * sense to have the dates sort be a tree, since it is always the worst
  416.  * case tree :(.  Oh well maybe in 4.0 someone will fix it 
  417.  */
  418. void ADDDATA(GOPHER_PTR data)
  419. {
  420.    if ((data->month >= mbegin) && (data->month <= mend))
  421.    {
  422.       hosts = Insert(hosts, data, HostsCmp);
  423.       docs  = Insert(docs, data, DocsCmp);
  424.       dates = Insert(dates, data, DatesCmp); /* Slow */
  425.       types = Insert(types, data, TypesCmp);
  426.       times = Insert(times, data, TimesCmp); 
  427.       days  = Insert(days, data, DaysCmp);
  428.       /* Small hack to have start and stop dates */
  429.       if (start_date[0] == '\0')
  430.       {
  431.          sprintf(start_date, "%s %s %d", Days[data->day-1], Months[data->month-1],
  432.           data->date);
  433.       }
  434.       sprintf(stop_date, "%s %s %d", Days[data->day-1], Months[data->month-1],
  435.           data->date);
  436.       TotalConnects++;
  437.    }
  438. }
  439.  
  440. /*******************************/
  441. /* Process a single line completely.  It checks to make sure it is a 
  442.  * Valid line, if not it inserts it into the cruft
  443.  */
  444. void ProcessLine(char *line)
  445. {
  446. GOPHER_PTR data;
  447. short len;
  448. char *temp; /* Used to save line, incase it is needed for cruft */
  449. char junk[1025];
  450. char message1[1024];
  451. char message2[1024];
  452.  
  453.    if (NULL == (data = (GOPHER_PTR)malloc(sizeof(GOPHER_REC))))
  454.    {
  455.       fprintf(stderr, "Not enough memory. Sorry\n");
  456.       exit(1);
  457.    }
  458.  
  459.    temp = line;
  460.  
  461.    temp = getf(temp, junk); /* Day */
  462.    if (8 == (data->day = DayStr2Num(junk))) 
  463.    { /* Not a real day of week */
  464.       free(data);
  465.       cruft = InsertErrorLog(cruft, line);
  466.       return;
  467.    }
  468.    temp = getf(temp, junk); /* Month */
  469.    if (13 == (data->month = MonthStr2Num(junk)))
  470.    { /* Not a real month */
  471.       free(data);
  472.       cruft = InsertErrorLog(cruft, line);
  473.       return;
  474.    }
  475.    temp = getf(temp, junk); /* Date */
  476.    data->date = atoi(junk);
  477.    temp = getf(temp, junk); /* Time */
  478.    junk[3] = '\0';
  479.    data->hour = atoi(junk); /* Hour */
  480.    temp = getf(temp, junk); /* Year */
  481.    temp = getf(temp, junk); /* What is this number ? */
  482.    temp = getf(temp ,junk); /* hostname */
  483.    if (junk[0] == ':')
  484.    { /* A colon in the hostfield */
  485.       free(data);
  486.       cruft = InsertErrorLog(cruft, line);
  487.       return;
  488.    } 
  489.  
  490.    if (NULL == (data->hostname = (char *)malloc(sizeof(char) * (strlen(junk)+1))))
  491.    {
  492.       fprintf(stderr, "Not enough memory. Sorry\n");
  493.       exit(1);
  494.    }
  495.    strcpy(data->hostname, junk);
  496.  
  497.    temp = getf(temp, junk); /* : COLON */
  498.    if (junk[0] != ':')
  499.    { /* Now we don't have a colon */
  500.       free(data->hostname);
  501.       free(data);
  502.       cruft = InsertErrorLog(cruft, line);
  503.       return;
  504.    } 
  505.    temp = getf(temp, message1);
  506.    temp = getf(temp, message2);
  507.    while((*temp == ' ') && (*temp != '\0'))
  508.        temp++;
  509.    data->path = (char *)malloc(sizeof(char)*(strlen(temp)+1));
  510.    strcpy(data->path, temp);
  511.    data->path[strlen(temp)] = '\0';
  512.  
  513.  
  514.    if (0 != (len = strlen(data->path)))
  515.    {
  516.       if (data->path[len-1] == '\n')
  517.          data->path[len-1] = '\0';
  518.    }
  519.  
  520. /* This one is for that annoying 0.0.0.* IP address then gets stuck
  521.  * in the log when someone is trying to access something you ain't got
  522.  */
  523.  
  524.    if (strncmp(data->hostname,"0.0.0", 5) == 0)
  525.    { 
  526.       free(data->path);
  527.       free(data->hostname);
  528.       free(data);
  529.       cruft = InsertErrorLog(cruft, line);
  530.       return;
  531.    } 
  532.  
  533.    if (strcmp(message1, "Root") == 0)
  534.    {
  535.       data->type = DIRTYPE;
  536.       free(data->path);
  537.       data->path = ROOTNAME;
  538.       ADDDATA(data);
  539.    }
  540.    else if ((strcmp(message1, "retrieved") == 0) && (strcmp(data->path, "/") == 0))
  541.    {
  542.       data->type = DIRTYPE;
  543.       free(data->path);
  544.       data->path = ROOTNAME;
  545.       ADDDATA(data);
  546.    }
  547.    else if (strcmp(message1, "search") == 0)
  548.    {
  549.       strcpy(junk, message2);
  550.       if (strncmp(data->path, "for ", 4) == 0)
  551.       {
  552.          /* We found it at the beginning of data->path */
  553.          temp = data->path;
  554.       }
  555.       else if (NULL == (temp = mystrstr(data->path, " for ")))
  556.       { /* No " for " in the search */
  557.          free(data->path);
  558.          free(data->hostname);
  559.          free(data);
  560.          cruft = InsertErrorLog(cruft, line);
  561.          return;
  562.       }
  563.       *temp = '\0'; /* Remove for stuff */
  564.       strcat(junk, " "); /* There is at least one space here */
  565.       strcat(junk, data->path);
  566.       free(data->path);
  567.       data->path = (char *)malloc(sizeof(char)*(strlen(junk)+1));
  568.       strcpy(data->path, junk);
  569.       data->type = SEARCHTYPE;
  570.  
  571.       ADDDATA(data);
  572.    }
  573.    else if (strncmp(message2, "ftp:", 4) == 0)
  574.    {
  575.       strcpy(junk, data->path); /* Incase there was a space in the path */
  576.       free(data->path); /* Then we have to save off path, since it contains it*/
  577.       data->path = (char *)malloc(sizeof(char)*(strlen(message2)+strlen(junk)-2));
  578.       strcpy(data->path, message2+4);
  579.       if (strlen(junk) > 0)
  580.       {
  581.          strcat(data->path, " ");
  582.          strcat(data->path, junk);
  583.       }
  584.       data->type = FTPTYPE;
  585.  
  586.       ADDDATA(data);
  587.    }
  588.    else if (strcmp(message1, "retrieved") == 0)
  589.    {
  590.       if (data->path[0] == '\0')
  591.       { /* We some how retrieved nothing */
  592.          free(data->path);
  593.          free(data->hostname);
  594.          free(data);
  595.          cruft = InsertErrorLog(cruft, line);
  596.          return;
  597.       } 
  598.  
  599.       if (strcmp(message2, "directory") == 0)
  600.          data->type = DIRTYPE;
  601.       else if (strcmp(message2, "maildir") == 0)
  602.          data->type = MAILDIRTYPE;
  603.       else if (strcmp(message2, "file") == 0)
  604.          data->type = FILETYPE;
  605.       else if (strcmp(message2, "binary") == 0)
  606.          data->type = BINARYTYPE;
  607.       else if (strcmp(message2, "sound") == 0)
  608.          data->type = SOUNDTYPE;
  609.       else if (strcmp(message2, "range") == 0)
  610.          data->type = RANGETYPE;
  611.       else
  612.       {
  613.          free(data->path);
  614.          free(data->hostname);
  615.          free(data);
  616.          cruft = InsertErrorLog(cruft, line);
  617.          return;
  618.       } 
  619.       ADDDATA(data);
  620.  
  621.    }
  622.    else /* wasn't anything we know about, g+ maybe?*/
  623.    {
  624.       free(data->path);
  625.       free(data->hostname);
  626.       free(data);
  627.       cruft = InsertErrorLog(cruft, line);
  628.       return;
  629.    } 
  630.  
  631.    return;
  632. }
  633.  
  634. /*******************************/
  635. void GatherInfo(void)
  636. {
  637. char line[1025];
  638.  
  639.    start_date[0] = '\0';
  640.  
  641.    while(!feof(stdin))
  642.    {
  643.       fgets(line, 1024, stdin);
  644.       if (feof(stdin))
  645.          break;
  646.       ProcessLine(line);
  647.    }
  648. }
  649.  
  650. /*******************************/
  651.  
  652. /* These vars are only valid right after a call to TreeTo?List.  I could have
  653.  *  done some fancy var passing, but why bother. :) 
  654.  */
  655. LIST_PTR GByNum;
  656. int GByNumMax;
  657. int GByNumMin; /* These two will be used for histograms in the future */
  658.     
  659.  
  660. /*******************************/
  661. void InsertSByNum(GOPHER_PTR data, short hits)
  662. {
  663. LIST_PTR temp, temp2;
  664.  
  665.    if (NULL == (temp = (LIST_PTR)malloc(sizeof(LIST_REC))))
  666.    {
  667.       fprintf(stderr, "Not enough memory in InsertByNum\n");
  668.       exit(1);
  669.    }
  670.    temp->data = data;
  671.    temp->next = NULL;
  672.    temp->hits = hits;
  673.  
  674. /* Figure out some vars */
  675.    if (hits < GByNumMin)
  676.       GByNumMin = hits;
  677.    if (hits > GByNumMax)
  678.       GByNumMax = hits;
  679.  
  680.    if (GByNum == NULL)
  681.       GByNum = temp;
  682.  
  683.    else if (GByNum->hits < hits)
  684.    {
  685.       temp->next = GByNum;
  686.       GByNum = temp;
  687.    }
  688.    else
  689.    {
  690.       temp2 = GByNum;
  691.       while (temp2->next != NULL)
  692.       {
  693.          if (temp2->next->hits < hits)
  694.      {
  695.         temp->next = temp2->next;
  696.         temp2->next = temp;
  697.         return;
  698.      }
  699.      temp2 = temp2->next;
  700.       }
  701.       temp2->next = temp;
  702.    }
  703. }
  704.  
  705. /*******************************/
  706. void InsertUByNum(GOPHER_PTR data, short hits)
  707. {
  708. LIST_PTR temp;
  709.  
  710.    if (NULL == (temp = (LIST_PTR)malloc(sizeof(LIST_REC))))
  711.    {
  712.       fprintf(stderr, "Not enough memory in InsertByNum\n");
  713.       exit(1);
  714.    }
  715.    temp->data = data;
  716.    temp->next = NULL;
  717.    temp->hits = hits;
  718.  
  719. /* Figure out some vars */
  720.    if (hits < GByNumMin)
  721.       GByNumMin = hits;
  722.    if (hits > GByNumMax)
  723.       GByNumMax = hits;
  724.  
  725.    if (GByNum == NULL)
  726.       GByNum = temp;
  727.  
  728.    else
  729.    {
  730.       temp->next = GByNum;
  731.       GByNum = temp;
  732.    }
  733. }
  734.  
  735. /*******************************/
  736. /* I did two different routines so it would be faster :).  I know this
  737.  * doesn't follow the logic of the rest of the program, but oh well.
  738.  * Do Inorder so that they remain in order, if two have the same
  739.  * num of hits 
  740.  */
  741. void TreeToSList(NODE_PTR tree)
  742. {
  743.    if (tree == NULL)
  744.       return;
  745.    
  746.    TreeToSList(tree->left);
  747.    InsertSByNum(tree->data, tree->hits);
  748.    TreeToSList(tree->right);
  749. }
  750.  
  751. /*******************************/
  752. void TreeToUList(NODE_PTR tree)
  753. {
  754. /* I did two different routines so it would be faster :).  I know this
  755.  * doesn't follow the logic of the rest of the program, but oh well.
  756.  * Do reverse inorder, so the insert just basicly sticks it at the
  757.  * beginning.  Someone should rewrite this, maybe later :)
  758.  */
  759.    if (tree == NULL)
  760.       return;
  761.    
  762.    TreeToUList(tree->right);
  763.    InsertUByNum(tree->data, tree->hits);
  764.    TreeToUList(tree->left);
  765. }
  766.  
  767. /*******************************/
  768. NODE_PTR ListToTree(LIST_PTR list, int cmp(GOPHER_PTR, GOPHER_PTR))
  769. {
  770. NODE_PTR temptree = NULL;
  771.    for(;list != NULL; list = list->next)
  772.       temptree = Insert(temptree, list->data, cmp);
  773.    return(temptree);
  774. }
  775. /*******************************/
  776. void FreeList(LIST_PTR list)
  777. {
  778. LIST_PTR temp;
  779.    while (list != NULL)
  780.    {
  781.       temp = list;
  782.       list = list->next;
  783.       free(temp);
  784.    }
  785. }
  786. /*******************************/
  787. void FreeTree(NODE_PTR tree)
  788. {
  789.    if (tree == NULL)
  790.       return;
  791.    FreeTree(tree->left);
  792.    FreeTree(tree->right);
  793. #ifndef NODETAIL
  794.    FreeList(tree->llist);
  795. #endif
  796.    free(tree);
  797.    return;
  798. }
  799. /*******************************/
  800. /* Given a string and and len, left justify and fill with fill */
  801. void printl(char *str, int len, char fill)
  802. {
  803.    while (len > 0)
  804.    {
  805.       if (*str == '\n')
  806.          str++;
  807.       if (*str == '\0')
  808.      putc(fill, stdout);
  809.       else
  810.          putc(*str++, stdout);
  811.       len--;
  812.    }
  813. }
  814. /*******************************/
  815. /* Given a string center justify and fill with fill */
  816. void printc(char *str, char fill)
  817. {
  818. int i, len = strlen(str);
  819. int start = (Width + WIDTHSUB - len)/2;
  820.  
  821.    for(i = 0; i < start; i++)
  822.       putc(fill, stdout);
  823.  
  824.    fputs(str, stdout);
  825.  
  826.    if (fill != ' ')
  827.       for(i = start+len; i < Width + WIDTHSUB ; i++)
  828.          putc(fill, stdout);
  829.    putc('\n', stdout);
  830. }
  831. /*******************************/
  832. void PrintData(GOPHER_PTR data)
  833. {
  834.    if (data == NULL)
  835.    {
  836.       printf("Data:\n");
  837.    }
  838.    else
  839.    {
  840.       printf("%c ",data->type);
  841.       printl(data->path, Width - 2, ' ');
  842.    }
  843. }
  844. /*******************************/
  845. void PrintTime(GOPHER_PTR data)
  846. {
  847.    if (data == NULL)
  848.    {
  849.       printf("Times:\n");
  850.    }
  851.    else
  852.    {
  853.       printf("%2d",data->hour);
  854.       printl("", Width - 2, ' ');
  855.    }
  856. }
  857. /*******************************/
  858. void PrintType(GOPHER_PTR data)
  859. {
  860. char *temp;
  861.    if (data == NULL)
  862.    {
  863.       printf("Types:\n");
  864.    }
  865.    else
  866.    {
  867.       switch(data->type)
  868.       {
  869.      case FILETYPE:
  870.         temp = "File";
  871.         break;
  872.      case SOUNDTYPE:
  873.         temp = "Sound";
  874.         break;
  875.      case BINARYTYPE:
  876.         temp = "Binary File";
  877.         break;
  878.      case DIRTYPE:
  879.         temp = "Directory";
  880.         break;
  881.      case MAILDIRTYPE:
  882.         temp = "Mail Directory";
  883.         break;
  884.      case FTPTYPE:
  885.         temp = "FTP";
  886.         break;
  887.      case RANGETYPE:
  888.         temp = "Range";
  889.         break;
  890.      case SEARCHTYPE:
  891.         temp = "Search";
  892.         break;
  893.          default:
  894.         temp = "Unknown";
  895.         break;
  896.       }
  897.       printl(temp, Width, ' ');
  898.    }
  899. }
  900. /*******************************/
  901. void PrintHost(GOPHER_PTR data)
  902. {
  903.    if (data == NULL)
  904.    {
  905.       printf("Hosts:\n");
  906.    }
  907.    else
  908.    {
  909.       printl(data->hostname, Width, ' ');
  910.    }
  911.  
  912. }
  913. /*******************************/
  914. void PrintDay(GOPHER_PTR data)
  915. {
  916.    if (data == NULL)
  917.    {
  918.       printf("Days:\n");
  919.    }
  920.    else
  921.    {
  922.       printl(Days[data->day-1], Width, ' ');
  923.    }
  924. }
  925. /*******************************/
  926. void PrintDate(GOPHER_PTR data)
  927. {
  928.    if (data == NULL)
  929.    {
  930.       printf("Dates:\n");
  931.    }
  932.    else
  933.    {
  934.       
  935.       printf("%3s %3s %d", Days[data->day-1], Months[data->month-1], data->date);
  936.       if (data->date < 10)
  937.          printl("\0", Width - 9, ' ');
  938.       else
  939.          printl("\0", Width - 10, ' ');
  940.    }
  941. }
  942. /*******************************/
  943. void PlotData(FILE *rfp, int num, GOPHER_PTR data)
  944. {
  945.    if (data == PLOTSTART)
  946.    {
  947.       fprintf(stderr, "Plot of Data is not currently supported, since I am not quite sure what it is suppose to do.  Mail me ideas: %s\n", EMAIL);
  948.  
  949.    }
  950.  
  951. }
  952. /*******************************/
  953. void PlotType(FILE *rfp, int num, GOPHER_PTR data)
  954. {
  955. char *temp = NULL;
  956.    if (data == PLOTSTART)
  957.    {
  958.       fprintf(rfp,"set xtics (");
  959.    }
  960.    else if (data == PLOTDONE)
  961.    {
  962.       fprintf(rfp,"\"\" %d)\n", num);
  963.       fprintf(rfp,"set data style linespoints\n");
  964.       fprintf(rfp,"set tics out\n");
  965.       fprintf(rfp,"set grid\n");
  966.       fprintf(rfp,"set title \"Gopher Usage\"\n");
  967.       fprintf(rfp,"plot \"%s.dat\"\n", base);
  968.    }
  969.    else
  970.    {
  971.       switch(data->type)
  972.       {
  973.      case FILETYPE:
  974.         temp = "File";
  975.         break;
  976.      case SOUNDTYPE:
  977.         temp = "Sound";
  978.         break;
  979.      case BINARYTYPE:
  980.         temp = "Binary File";
  981.         break;
  982.      case DIRTYPE:
  983.         temp = "Directory";
  984.         break;
  985.      case MAILDIRTYPE:
  986.         temp = "Mail Directory";
  987.         break;
  988.      case FTPTYPE:
  989.         temp = "FTP";
  990.         break;
  991.      case RANGETYPE:
  992.         temp = "Range";
  993.         break;
  994.      case SEARCHTYPE:
  995.         temp = "Search";
  996.         break;
  997.          default:
  998.         temp = "Unknown";
  999.         break;
  1000.       }
  1001.       fprintf(rfp,"\"%s\" %d,", temp, num);
  1002.    }
  1003.  
  1004. }
  1005. /*******************************/
  1006. void PlotHost(FILE *rfp, int num, GOPHER_PTR data)
  1007. {
  1008.    if (data == PLOTSTART)
  1009.    {
  1010.       fprintf(stderr, "Plot of Hosts is not currently supported, since I am not quite sure what it is suppose to do.  Mail me ideas: %s\n", EMAIL);
  1011.  
  1012.    }
  1013. }
  1014. /*******************************/
  1015. void PlotDay(FILE *rfp, int num, GOPHER_PTR data)
  1016. {
  1017.    if (data == PLOTSTART)
  1018.    {
  1019.       fprintf(rfp,"set xtics (");
  1020.    }
  1021.    else if (data == PLOTDONE)
  1022.    {
  1023.       fprintf(rfp,"\"\" %d)\n", num);
  1024.       fprintf(rfp,"set data style linespoints\n");
  1025.       fprintf(rfp,"set tics out\n");
  1026.       fprintf(rfp,"set grid\n");
  1027.       fprintf(rfp,"set title \"Gopher Usage\"\n");
  1028.       fprintf(rfp,"plot \"%s.dat\"\n", base);
  1029.    }
  1030.    else
  1031.    {
  1032.       fprintf(rfp,"\"%s\" %d,",Days[data->day-1], num);
  1033.    }
  1034. }
  1035. /*******************************/
  1036. void PlotTime(FILE *rfp, int num, GOPHER_PTR data)
  1037. {
  1038.    if (data == PLOTSTART)
  1039.    {
  1040.       fprintf(rfp,"set xtics (");
  1041.    }
  1042.    else if (data == PLOTDONE)
  1043.    {
  1044.       fprintf(rfp,"\"\" %d)\n", num);
  1045.       fprintf(rfp,"set data style linespoints\n");
  1046.       fprintf(rfp,"set tics out\n");
  1047.       fprintf(rfp,"set grid\n");
  1048.       fprintf(rfp,"set title \"Gopher Usage\"\n");
  1049.       fprintf(rfp,"plot \"%s.dat\"\n", base);
  1050.    }
  1051.    else
  1052.    {
  1053.       fprintf(rfp,"\"%2d\" %d,",data->hour, num);
  1054.    }
  1055. }
  1056. /*******************************/
  1057. void PlotDate(FILE *rfp, int num, GOPHER_PTR data)
  1058. {
  1059.    if (data == PLOTSTART)
  1060.    {
  1061.       fprintf(rfp,"set xtics (");
  1062.    }
  1063.    else if (data == PLOTDONE)
  1064.    {
  1065.       fprintf(rfp,"\"\" %d)\n", num);
  1066.       fprintf(rfp,"set data style linespoints\n");
  1067.       fprintf(rfp,"set tics out\n");
  1068.       fprintf(rfp,"set grid\n");
  1069.       fprintf(rfp,"set title \"Gopher Usage\"\n");
  1070.       fprintf(rfp,"plot \"%s.dat\"\n", base);
  1071.    }
  1072.    else
  1073.    {
  1074.       if ((data->date == 1) || (data->date == 15) || (num == 1))
  1075.          fprintf(rfp,"\"%s/%d\" %d,",Months[data->month-1], data->date, num);
  1076.    }
  1077. }
  1078. #ifndef NODETAIL
  1079. /*******************************/
  1080. void DoDetail(NODE_PTR tree, byte DetailType)
  1081. {
  1082. NODE_PTR newtree;
  1083.    switch(DetailType)
  1084.    {
  1085.    case DATAINFO:
  1086.       newtree = ListToTree(tree->llist, DocsCmp);
  1087.       PrintInfo(newtree, PrintData, DocsCmp, DETAILINFO);
  1088.       break;
  1089.    case HOSTINFO:
  1090.       newtree = ListToTree(tree->llist, HostsCmp);
  1091.       PrintInfo(newtree, PrintHost, HostsCmp, DETAILINFO);
  1092.       break;
  1093.    case WEEKDAYINFO:
  1094.       newtree = ListToTree(tree->llist, DaysCmp);
  1095.       PrintInfo(newtree, PrintDay, DaysCmp, DETAILINFO);
  1096.       break;
  1097.    case MONTHDATEINFO:
  1098.       newtree = ListToTree(tree->llist, DatesCmp);
  1099.       PrintInfo(newtree, PrintDate, DatesCmp, DETAILINFO);
  1100.       break;
  1101.    case TYPEINFO:
  1102.       newtree = ListToTree(tree->llist, TypesCmp);
  1103.       PrintInfo(newtree, PrintType, TypesCmp, DETAILINFO);
  1104.       break;
  1105.    case TIMEINFO:
  1106.       newtree = ListToTree(tree->llist, TimesCmp);
  1107.       PrintInfo(newtree, PrintTime, TimesCmp, DETAILINFO);
  1108.       break;
  1109.    default:
  1110.       newtree = NULL;
  1111.       break;
  1112.    }
  1113.    FreeTree(newtree);
  1114. }
  1115. #endif
  1116. /*******************************/
  1117. void PrintInfo(NODE_PTR tree, void print(GOPHER_PTR), int cmp(GOPHER_PTR a, GOPHER_PTR b), byte DetailType)
  1118. {
  1119. LIST_PTR temp;
  1120. LIST_PTR ByNum;
  1121.  
  1122.    GByNum = NULL; /* Init the vars for the TreeToList function */
  1123.    TreeToSList(tree);
  1124.  
  1125.    if (DetailType != DETAILINFO)
  1126.    { /* We are not printing Detail info now, so do headers */
  1127.       print(NULL);
  1128.       printc("", '=');
  1129.    }
  1130.  
  1131.    ByNum = GByNum; /* Save off and clear the globals vars, since this */
  1132.    GByNum = NULL;
  1133.  
  1134.    temp = ByNum;
  1135.    while (temp != NULL)
  1136.    {
  1137. #ifndef NODETAIL
  1138.       if (DetailType == DETAILINFO)
  1139.          printf("   ");
  1140. #endif
  1141.  
  1142.       print(temp->data);
  1143.       printf(" %4d (%2.2f%%)\n", temp->hits, (float)temp->hits*100.0/TotalConnects);
  1144. #ifndef NODETAIL
  1145.       if ((DetailType != NOINFO) && (DetailType != DETAILINFO))
  1146.          DoDetail(Find(tree, temp->data, cmp), DetailType);
  1147.      /* Don't generate Detail for NOINFO or if we are already doing detail */
  1148. #endif
  1149.       temp = temp->next;
  1150.    }
  1151.    printf("\n");
  1152.    FreeList(ByNum);
  1153. }
  1154. /*******************************/
  1155. void PlotInfo(NODE_PTR tree, void plot(FILE *, int, GOPHER_PTR))
  1156. {
  1157. LIST_PTR temp;
  1158. FILE *rfp, *dfp;
  1159. char *fn;
  1160. int points = 1;
  1161.  
  1162.    if (OutPlot == GNUOUT)
  1163.    {
  1164.       fn = (char *)malloc(strlen(base) + 5);
  1165.       sprintf(fn,"%s.run", base);
  1166.       if (NULL == (rfp = fopen(fn, "w")))
  1167.       {
  1168.          fprintf(stderr, "Could not open file \"%s\" for plot run\n", fn);
  1169.          free(fn);
  1170.      return;
  1171.       }
  1172.       sprintf(fn,"%s.dat", base);
  1173.       if (NULL == (dfp = fopen(fn, "w")))
  1174.       {
  1175.          fprintf(stderr, "Could not open file \"%s\" for plot data\n", fn);
  1176.          free(fn);
  1177.      return;
  1178.       }
  1179.       free(fn);
  1180.    }
  1181.    else
  1182.    {
  1183.       rfp = stdout;
  1184.       dfp = stdout;
  1185.    }
  1186.  
  1187.    plot(rfp, 0, PLOTSTART);
  1188.    GByNum = NULL; /* Init the vars for the TreeToList function */
  1189.    GByNumMax = 0;
  1190.    GByNumMin = 36000;
  1191.    TreeToUList(tree);
  1192.  
  1193.    temp = GByNum;
  1194.    while (temp != NULL)
  1195.    {
  1196.       plot(rfp, points, temp->data);
  1197.       fprintf(dfp, "%d %d\n", points++, temp->hits);
  1198.       temp = temp->next;
  1199.    }
  1200.    plot(rfp, points, PLOTDONE);
  1201.    printf("\n");
  1202.    FreeList(GByNum);
  1203. }
  1204. /*******************************/
  1205. void PrintErrorInfo(void)
  1206. {
  1207. ELIST_PTR temp = cruft;
  1208.  
  1209.    printf("Exception/Problem Report\n");
  1210.    printf("NOTE: THESE ENTRIES MAY DENOTE A SERVER PROBLEM. THEY SHOULD BE LOOKED OVER!\n");
  1211.    printc("", '=');
  1212.    while (temp != NULL)
  1213.    {
  1214.       printf(temp->data);
  1215.       temp = temp->next;
  1216.    }
  1217.    printf("\n");
  1218. }
  1219.  
  1220. /*******************************/
  1221.  
  1222. void PrintHelp(void)
  1223. {
  1224. #ifndef NODETAIL
  1225.    fprintf(stderr,"Usage: glog -%c [-<SORTTYPE>[<SORTTYPE>]] [OPTIONS] [<logfile]\n", ERRORINFO);
  1226. #else
  1227.    fprintf(stderr,"Usage: glog -%c [-<SORTTYPE>] [OPTIONS] [<logfile]", ERRORINFO);
  1228. #endif
  1229.    
  1230.    fprintf(stderr, "  glog -h prints this help information\n");
  1231.    fprintf(stderr, "  glog -%c displays an ERROR LOG\n",ERRORINFO);
  1232.    fprintf(stderr, "  stdin is expected to be your gopher logfile\n\n");
  1233.    fprintf(stderr, "SORTTYPE is one of the following\n");
  1234.    fprintf(stderr, "   %c = Host Names       %c = Day of Week \n",
  1235.       HOSTINFO, WEEKDAYINFO);
  1236.    fprintf(stderr, "   %c = Document Names   %c = Month/Day\n",
  1237.     DATAINFO, MONTHDATEINFO);
  1238.    fprintf(stderr, "   %c = Type             %c = Time\n\n", TYPEINFO, TIMEINFO);
  1239.    fprintf(stderr, "OPTIONS is one of the following\n");
  1240.    fprintf(stderr, "   [-b <beginning month #>] [-e <ending month #>]\n");
  1241.    fprintf(stderr, "   [-w <width of report>] [-p <SORTTYPE>] PLOT <by SORTTYPE>\n");
  1242.    fprintf(stderr, "   [-f <base filename for plot>] [-o <plot outputtype>] (currently unsupported)\n\n");
  1243.    fprintf(stderr, "WARNING: -b and -e are applied to the whole command,\n"); 
  1244.    fprintf(stderr, "   any other arguments are applied left to right\n\n");
  1245. }
  1246.  
  1247. /*******************************/
  1248. int main(int argc, char **argv)
  1249. {
  1250. int i;
  1251. char center[80];
  1252.  
  1253. #ifdef THINK_C
  1254.     argc = ccommand(&argv); 
  1255. #endif   
  1256.    printf("\n");
  1257.    printc(GLOG_VERSION,' ');
  1258.    fflush(stdout);
  1259.  
  1260.    if  (1 == argc || argv[1][1] =='h')
  1261.    {
  1262.       PrintHelp(); /* Clueless */
  1263.       exit(-1);
  1264.    }
  1265.  
  1266.    i = 1;
  1267. /* We must go through the arguments twice.  Once for the the arguments that
  1268.  * can only be set once.  I coult add argument checking here, but why bother
  1269.  */
  1270.    while (i<argc)
  1271.    {
  1272.       if (argv[i][0] == '-')
  1273.       switch (argv[i][1])
  1274.       {
  1275.  
  1276.       case 'b':
  1277.          if (i<argc-1)
  1278.         mbegin = atoi(argv[++i]);
  1279.      else
  1280.      {
  1281.         fprintf(stderr, "\nexpected beginning month argument for -%c\n", argv[i-1][1]);
  1282.         PrintHelp();
  1283.      }
  1284.          break;
  1285.       case 'e':
  1286.          if (i<argc-1)
  1287.         mend = atoi(argv[++i]);
  1288.      else
  1289.      {
  1290.         fprintf(stderr, "\nexpected ending month argument for -%c\n", argv[i-1][1]);
  1291.         PrintHelp();
  1292.      }
  1293.          break;
  1294.       }
  1295.       i++;
  1296.    }
  1297.    GatherInfo();
  1298.    sprintf(center, "%s to %s", start_date, stop_date);
  1299.    printc(center, ' ');
  1300.    sprintf(center, "%d Connections", TotalConnects);
  1301.    printc(center, ' ');
  1302.    printf("\n\n");
  1303.    fflush(stdout);
  1304.    fflush(stderr);
  1305.  
  1306.    i = 1;
  1307.    /* Now go through them again and actually do them, from left to right */
  1308.    while (i<argc)
  1309.    {
  1310.       switch (argv[i][1])
  1311.       {
  1312.       case ERRORINFO:
  1313.      PrintErrorInfo();
  1314.      break;
  1315.       case DATAINFO:
  1316.      PrintInfo(docs, PrintData, DocsCmp, argv[i][2]);
  1317.      break;
  1318.       case TYPEINFO:
  1319.      PrintInfo(types, PrintType, TypesCmp, argv[i][2]);
  1320.      break;
  1321.       case TIMEINFO:
  1322.      PrintInfo(times, PrintTime, TimesCmp, argv[i][2]);
  1323.      break;
  1324.       case WEEKDAYINFO:
  1325.          PrintInfo(days, PrintDay, DaysCmp, argv[i][2]);
  1326.      break;
  1327.       case MONTHDATEINFO:
  1328.      PrintInfo(dates, PrintDate, DatesCmp, argv[i][2]);
  1329.      break;
  1330.       case HOSTINFO:
  1331.      PrintInfo(hosts, PrintHost, HostsCmp, argv[i][2]);
  1332.      break;
  1333.       case 'p': /*custom plots*/
  1334.          switch (argv[i][2])
  1335.      {
  1336.          case DATAINFO:
  1337.             PlotInfo(docs, PlotData);
  1338.         break;
  1339.          case TYPEINFO:
  1340.         PlotInfo(types, PlotType);
  1341.         break;
  1342.          case TIMEINFO:
  1343.         PlotInfo(times, PlotTime);
  1344.         break;
  1345.          case WEEKDAYINFO:
  1346.         PlotInfo(days, PlotDay);
  1347.         break;
  1348.          case MONTHDATEINFO:
  1349.         PlotInfo(dates, PlotDate);
  1350.         break;
  1351.          case HOSTINFO:
  1352.         PlotInfo(hosts, PlotHost);
  1353.         break;
  1354.      }
  1355.       break;
  1356.       case 'w':
  1357.          if (i<argc-1)
  1358.         Width = atoi(argv[++i]) - WIDTHSUB;
  1359.      else
  1360.      {
  1361.         fprintf(stderr, "expected width argument for -%c\n", argv[i-1][1]);
  1362.         PrintHelp();
  1363.      }
  1364.      break;
  1365.       case 'o':
  1366.          if (i<argc-1)
  1367.         OutPlot = atoi(argv[++i]);
  1368.      else
  1369.      {
  1370.         fprintf(stderr, "expected output type argument for -%c\n", argv[i-1][1]);
  1371.         PrintHelp();
  1372.      }
  1373.      break;
  1374.       case 'f':
  1375.          if (i<argc-1)
  1376.         base = argv[++i];
  1377.      else
  1378.      {
  1379.         fprintf(stderr, "expected filename argument for -%c\n", argv[i-1][1]);
  1380.         PrintHelp();
  1381.      }
  1382.      break;
  1383.       case 'b':
  1384.       case 'e':
  1385.          i++;   /* Skip over their arguments */
  1386.          break; /* These are before GatherInfo arguments */
  1387.       case '?':
  1388.       default:
  1389.          fprintf(stderr, "Unknown option \"%c\" .  -h for help\n", argv[i][1]);
  1390.      break;
  1391.       } /*switch*/
  1392.     
  1393.       i++; /* next arg...*/
  1394.         
  1395.    } /*while*/
  1396.   
  1397.    exit(0);
  1398. }
  1399. /*******************************/
  1400.